In [1]:
pip install plotly
Requirement already satisfied: plotly in /opt/conda/lib/python3.8/site-packages (4.10.0)
Requirement already satisfied: retrying>=1.3.3 in /opt/conda/lib/python3.8/site-packages (from plotly) (1.3.3)
Requirement already satisfied: six in /opt/conda/lib/python3.8/site-packages (from plotly) (1.15.0)
Note: you may need to restart the kernel to use updated packages.
In [2]:
from collections import defaultdict
from typing import List, Set, Tuple
from fractions import Fraction
In [3]:
def edges_to_vertices(edges: List[Tuple]) -> List[int]:
    vertices = []
    for edge in edges:
        for vertex in edge:
            if vertex not in vertices:
                vertices.append(vertex)
    return vertices

def compute_interior_angle(n_sides: int) -> Fraction:
    return Fraction(1, 2) - Fraction(1, n_sides)

class NotEnoughRoomError(Exception):
    pass

def getitem(data):
    lst = list(data)
    if len(lst) > 1:
        raise ValueError(data)
    return lst[0]

class NoMovesError(Exception):
    pass

class ImpossibleError(Exception):
    pass


class Tiling:
    def __init__(self, pattern):
        self.pattern = pattern
        self.shapes = []
        self.edges_to_shapes = defaultdict(lambda : [])
        self.vertices_to_edges = defaultdict(lambda : [])
        self.next_new_vertex = 0
        
    def list_possible_shapes(self, vertex: int) -> List[int]:
        edges = self.vertices_to_edges[vertex]
        shape_ids = set.union(*[set(self.edges_to_shapes[edge]) for edge in edges])

        angle_sum = 0
        remaining_pattern = list(self.pattern)
        for shape_id in shape_ids:
            shape = self.shapes[shape_id]
            angle_sum += shape["angle"]
            remaining_pattern.pop(remaining_pattern.index(shape["n_sides"]))

        required_angle = sum([compute_interior_angle(n_sides) for n_sides in remaining_pattern])
        if angle_sum + required_angle > 1:
            raise NotEnoughRoomError(vertex)
        return remaining_pattern
    
    def add_first_shape(self, n_sides):
        if len(self.shapes) > 0:
            raise RuntimeError("already initialised")
        edges = []
        for k in range(n_sides - 1):
            edges.append((k, k + 1))
        edges.append((0, k + 1))
        
        self._add_shape(edges)
        
    def add_shape_to_edge(self, n_sides: int, edge: Tuple, debug=False):
        edges = self.compute_new_edges(n_sides, edge, debug=debug)
        self._add_shape(edges)

    def _add_shape(self, edges: List[Tuple]):
        n_sides = len(edges)
        vertices = []

        shape_id = len(self.shapes)  # next shape

        for edge in edges:
            self.edges_to_shapes[edge].append(shape_id)
            for vertex in edge:
                self.vertices_to_edges[vertex].append(edge)

        vertices = edges_to_vertices(edges)
        self.shapes.append({
            "edges": edges,
            "vertices": vertices,
            "angle": compute_interior_angle(n_sides),
            "n_sides": n_sides,
        })
        
        self.next_new_vertex = max(max(vertices) + 1, self.next_new_vertex)
        
    def list_open_edges(self, vertex, ignore_vertices=()):
        return [
            edge for edge in self.vertices_to_edges[vertex]
            if len(self.edges_to_shapes[edge]) == 1
            if edge[0] not in ignore_vertices
            if edge[1] not in ignore_vertices
        ]
    
    def get_possible_shapes_for_edge(self, edge) -> List[int]:
        possible_shapes0 = self.list_possible_shapes(edge[0])
        possible_shapes1 = self.list_possible_shapes(edge[1])
        set_possible_shapes = set(possible_shapes0) & set(possible_shapes1)
        return list(set_possible_shapes)
        

    def compute_new_edges(self, n_sides, base_edge, debug=False):
        vertices = list(base_edge)  # order matters, describes adjacency
        edges = [base_edge]
        new_vertex = self.next_new_vertex
        
        if debug:
            breakpoint()
        
        allow_new_vertex = False
        while len(vertices) < n_sides:
            this_vertex = vertices[-1]
            open_edges = self.list_open_edges(this_vertex, ignore_vertices=[vertices[-2]])  # ignore inside vertex

            # no pre-existing edge
            if not open_edges:
                if allow_new_vertex:
                    next_vertex = new_vertex
                    new_vertex += 1
                else:
                    # try the other direction first
                    vertices = list(reversed(vertices))
                    allow_new_vertex = True
                    continue

            #  a pre-existing edge exists
            elif len(open_edges) == 1:

                # this edge is part of the new shape
                if len(self.list_possible_shapes(this_vertex)) == 1: 
                    next_vertex = getitem([v for v in open_edges[0] if v != this_vertex])
                    next_vertex_shapes = self.list_possible_shapes(next_vertex)
                    assert n_sides in next_vertex_shapes, f"could not place sides={n_sides} at vertex={next_vertex}"

                # There will be more new shapes between this new shape and the
                # pre-existing edge. 
                # NOTE: This assumes that there are only ever two open edges on any vertex.
                # This is (hopefully) guaranteed by:
                #   - building breadth-first
                #   - always attaching new shapes to at least one pre-existing edge
                else:
                    if allow_new_vertex:
                        next_vertex = new_vertex
                        new_vertex += 1
                    else:
                        # try the other direction first
                        vertices = list(reversed(vertices))
                        allow_new_vertex = True
                        continue

            # should be impossible        
            else:  
                raise ValueError(f"too many open edges on vertex={this_vertex}")

            vertices.append(next_vertex)
            allow_new_vertex = False
            # Next time, add on the other side.
            # If we only built from one side, it would be really hard to tell
            # if we should connect to adajacent open edges on the far side
            vertices = list(reversed(vertices))
            
            edges.append(tuple(sorted([this_vertex, next_vertex])))
        
        # close the shape
        edges.append(tuple(sorted([vertices[0], vertices[-1]])))
        assert len(edges) == n_sides  # sanity check!
        return edges
    
    def iter_open_edges(self):
        for edge in self.edges_to_shapes:  # breadth-first
            if len(self.edges_to_shapes[edge]) == 2:
                # this edge already has two shapes attached
                continue
            yield edge
        

    def run(self, n_steps, debug=False):
        counter = 0
        while counter < n_steps:
            saved_n_sides = None
            saved_edge = None
            for edge in self.iter_open_edges():  # breadth-first
                
                possible_shapes = self.get_possible_shapes_for_edge(edge)

                if len(possible_shapes) == 1:
                    n_sides = possible_shapes.pop()
                    # always pick the smallest avaliable shape to avoid non-local interactions
                    if n_sides == min(self.pattern):
                        saved_n_sides = n_sides
                        saved_edge = edge
                        break
                    elif saved_n_sides is None:
                        saved_n_sides = n_sides
                        saved_edge = edge
                        continue
                    elif n_sides < saved_n_sides:
                        saved_n_sides = n_sides
                        saved_edge = edge
                        continue
                elif len(possible_shapes) == 0:
                    raise ImpossibleError(edge)
                    
            if saved_n_sides is None:
                raise NoMovesError
            else:
                self.add_shape_to_edge(saved_n_sides, saved_edge, debug=debug)
                    
            counter += 1
In [4]:
import math
import plotly

def iter_vertices_on_edges(base_edge, edges):
    remaining_edges = set(edges)
    remaining_edges.remove(base_edge)
    # v0 = previous vertex
    # v1 = pivot vertex
    # v2 = next vertex
    # arbitrarily pick base_edge[1] as pivot
    v0, v1 = base_edge
    while remaining_edges:
        next_edge = getitem([
            e for e in remaining_edges 
            if v1 in e
        ])
        v2 = getitem([v for v in next_edge if v != v1])
        yield v0, v1, v2, next_edge
        remaining_edges.remove(next_edge)
        v0, v1 = v1, v2

        
class Plot:
    def __init__(self, tiling):
        self.vertices_to_coords = {
            0: (0, 0),
            1: (1, 0),
        }
        self.edges = [(0, 1)]
        self.rendered_shapes = {}
        
        for shape_id, shape_info in enumerate(tiling.shapes):
            edges = shape_info["edges"]

            base_edge = edges[0]  # first edge is always shared edge with shape on which this shape is bolted onto
            assert base_edge in self.edges

            theta = math.pi - shape_info["angle"] * 2 * math.pi
            if shape_id > 0:
                adjacent_shape_id = getitem([
                    s for s in tiling.edges_to_shapes[base_edge]
                    if s != shape_id
                ])
                adjacent_shape_edges = tiling.shapes[adjacent_shape_id]["edges"]
                v0, v1, v2, _ = next(iter_vertices_on_edges(base_edge, adjacent_shape_edges))
                x0, y0 = self.vertices_to_coords[v0]
                x1, y1 = self.vertices_to_coords[v1]
                x2, y2 = self.vertices_to_coords[v2]
                x1hat, y1hat = x1 - x0, y1 - y0
                x2hat, y2hat = x2 - x0, y2 - y0
                n_x1hat, n_y1hat = -y1hat, x1hat  # anti-clockwise normal
                if n_x1hat * x2hat + n_y1hat * y2hat < 0: # i.e. adjacent shape turns clockwise
                    # NB dot product can't be zero because turning angle < 180 for all regular polygons
                    # make anti-clockwise turns
                    theta *= -1

            for v0, v1, v2, edge in iter_vertices_on_edges(base_edge, edges):
                assert v0 in self.vertices_to_coords
                assert v1 in self.vertices_to_coords

                if edge in self.edges:
                    continue
                elif v2 in self.vertices_to_coords:
                    self.edges.append(edge)
                    continue

                x0, y0 = self.vertices_to_coords[v0]
                x1, y1 = self.vertices_to_coords[v1]
                # shift origin to (x0, y0)
                x1hat, y1hat = x1 - x0, y1 - y0
                # rotate theta from (x0, y0)
                x2hat = x1hat * (math.cos(theta) + 1) + y1hat * math.sin(theta)
                y2hat = - x1hat * math.sin(theta) + y1hat * (math.cos(theta) + 1)
                # back to global coords
                x2, y2 = x2hat + x0, y2hat + y0

                self.edges.append(edge)
                self.vertices_to_coords[v2] = (x2, y2)
            self.rendered_shapes[shape_id] = edges
            
    def plotly_iplot(self, label_vertices=False):
        text = []
        x = []
        y = []
        for v, (x_, y_) in self.vertices_to_coords.items():
            text.append(f"<b>{v}</b>")
            x.append(x_)
            y.append(y_)


        fig = {
            "data": [
                {
                    "type": "scatter", "x": x, "y": y, 
                    "mode": "markers+text" if label_vertices else "markers", "name": "",
                    "text": text, "hovertemplate": "%{text}",
                    "textposition": "top right",
                    "textfont_size": 12
                },
            ],
            "layout": {

                "xaxis": dict(
                    zeroline=False, showticklabels=False, ticks="", showgrid=False,
                    # tickmode = 'linear', dtick = 0.5
                ),
                "yaxis": dict(
                    scaleanchor="x", scaleratio=1, 
                    zeroline=False, showticklabels=False, ticks="", showgrid=False,
                    # tickmode = 'linear', dtick = 0.5,
                ),
                "shapes": [
                    {
                        'line': {'color': 'RoyalBlue', 'width': 3},
                        'type': 'line',
                        'x0': self.vertices_to_coords[v0][0], 'x1': self.vertices_to_coords[v1][0],
                        'y0': self.vertices_to_coords[v0][1], 'y1': self.vertices_to_coords[v1][1],
                        'xref': 'x', 'yref': 'y'
                    }
                    for (v0, v1) in self.edges
                ],
                "height": 800,
                "width": 800,
            },
        }

        plotly.offline.iplot(fig)
In [5]:
tiling = Tiling([12, 4, 3, 3])
tiling.add_first_shape(4)
tiling.add_shape_to_edge(3, (0, 1))
tiling.add_shape_to_edge(3, (1, 2))
tiling.run(23)
# tiling.add_shape_to_edge(3, (2, 5))
# tiling.run(7)

plot = Plot(tiling)
plot.plotly_iplot(True)
In [6]:
tiling = Tiling([12, 4, 3, 3])
tiling.add_first_shape(4)
tiling.add_shape_to_edge(12, (0, 1))
tiling.run(8)

plot = Plot(tiling)
plot.plotly_iplot(True)
In [7]:
tiling = Tiling([12, 4, 3, 3])
tiling.add_first_shape(4)
tiling.add_shape_to_edge(12, (0, 1))
tiling.add_shape_to_edge(12, (2, 3))
tiling.run(4)

plot = Plot(tiling)
plot.plotly_iplot(True)
In [8]:
tiling = Tiling([6, 6, 3, 3])
tiling.add_first_shape(6)
tiling.add_shape_to_edge(3, (0, 1))
tiling.add_shape_to_edge(6, (1, 2))
tiling.run(35)

plot = Plot(tiling)
plot.plotly_iplot()
In [9]:
tiling = Tiling([6, 6, 3, 3])
tiling.add_first_shape(6)
tiling.add_shape_to_edge(3, (0, 1))
tiling.add_shape_to_edge(3, (1, 2))
tiling.add_shape_to_edge(3, (2, 3))
tiling.add_shape_to_edge(3, (3, 4))
tiling.add_shape_to_edge(3, (4, 5))
tiling.add_shape_to_edge(3, (0, 5))
tiling.run(12)

plot = Plot(tiling)
plot.plotly_iplot()
In [10]:
tiling = Tiling([4, 4, 4, 4])
tiling.add_first_shape(4)
tiling.run(100)

plot = Plot(tiling)
plot.plotly_iplot()
In [11]:
tiling = Tiling([3, 12, 12])
tiling.add_first_shape(3)
tiling.run(25)

plot = Plot(tiling)
plot.plotly_iplot()
In [12]:
tiling = Tiling([4, 8, 8])
tiling.add_first_shape(4)
tiling.run(100)

plot = Plot(tiling)
plot.plotly_iplot()
In [13]:
tiling = Tiling([6, 4, 12])
tiling.add_first_shape(12)
tiling.add_shape_to_edge(4, (0, 1))
tiling.add_shape_to_edge(6, (1, 2))
tiling.run(102)

plot = Plot(tiling)
plot.plotly_iplot()
In [14]:
tiling = Tiling([6, 6, 6])
tiling.add_first_shape(6)
tiling.run(101)

plot = Plot(tiling)
plot.plotly_iplot()
In [15]:
tiling = Tiling([6, 3, 3, 3, 3])
tiling.add_first_shape(6)
tiling.add_shape_to_edge(3, (0, 1))
tiling.add_shape_to_edge(3, (1, 6))
tiling.add_shape_to_edge(6, (6, 7))
tiling.run(300)

plot = Plot(tiling)
plot.plotly_iplot()
In [16]:
tiling = Tiling([6, 3, 3, 3, 3])
tiling.add_first_shape(6)
tiling.add_shape_to_edge(3, (0, 1))
tiling.add_shape_to_edge(3, (0, 6))
tiling.add_shape_to_edge(6, (6, 7))
tiling.run(300)

plot = Plot(tiling)
plot.plotly_iplot()
In [17]:
tiling = Tiling([3, 3, 3, 4, 4])
tiling.add_first_shape(4)
tiling.add_shape_to_edge(4, (0, 1))
tiling.run(6)

plot = Plot(tiling)
plot.plotly_iplot()
In [18]:
tiling = Tiling([3, 3, 3, 4, 4])
tiling.add_first_shape(4)
tiling.add_shape_to_edge(3, (0, 1))
tiling.add_shape_to_edge(3, (1, 2))
tiling.add_shape_to_edge(3, (2, 3))
tiling.add_shape_to_edge(4, (1, 4))
tiling.add_shape_to_edge(4, (0, 4))
tiling.run(5)

plot = Plot(tiling)
plot.plotly_iplot()
In [19]:
tiling = Tiling([3, 3, 3, 3, 3, 3])
tiling.add_first_shape(3)
tiling.run(150)

plot = Plot(tiling)
plot.plotly_iplot()
In [20]:
        
def meta_tiling(tiling, n_steps, update_func):
    n = 0
    while n < n_steps:
        try:
            tiling.run(1)
        except NoMovesError:
            print(f"hit {update_func.__name__} on move={n}")
            update_func(tiling)
        n += 1
In [21]:
def tri_on_hex(tiling: Tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 6:
            if 3 in tiling.get_possible_shapes_for_edge(edge):
                tiling.add_shape_to_edge(3, edge)
                return
    raise NoMovesError
    
    
tiling = Tiling([6, 6, 3, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 192, tri_on_hex)

plot = Plot(tiling)
plot.plotly_iplot()
hit tri_on_hex on move=0
hit tri_on_hex on move=1
hit tri_on_hex on move=3
hit tri_on_hex on move=6
hit tri_on_hex on move=9
hit tri_on_hex on move=12
hit tri_on_hex on move=18
hit tri_on_hex on move=21
hit tri_on_hex on move=27
hit tri_on_hex on move=33
hit tri_on_hex on move=39
hit tri_on_hex on move=45
hit tri_on_hex on move=54
hit tri_on_hex on move=60
hit tri_on_hex on move=69
hit tri_on_hex on move=78
hit tri_on_hex on move=87
hit tri_on_hex on move=96
hit tri_on_hex on move=108
hit tri_on_hex on move=117
hit tri_on_hex on move=129
hit tri_on_hex on move=141
hit tri_on_hex on move=153
hit tri_on_hex on move=165
hit tri_on_hex on move=180
In [22]:
def hex_on_hex(tiling: Tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 6:
            if 6 in tiling.get_possible_shapes_for_edge(edge):
                tiling.add_shape_to_edge(6, edge)
                return
    raise NoMovesError
    
    
tiling = Tiling([6, 6, 3, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 23, hex_on_hex)

plot = Plot(tiling)
plot.plotly_iplot()
hit hex_on_hex on move=0
In [23]:
tiling = Tiling([6, 6, 3, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 10, tri_on_hex)
meta_tiling(tiling, 20, hex_on_hex)
# meta_tiling(tiling, 200, tri_on_hex)
while True:
    try:
        tri_on_hex(tiling)
    except NoMovesError:
        break

meta_tiling(tiling, 80, tri_on_hex)
plot = Plot(tiling)
plot.plotly_iplot()
hit tri_on_hex on move=0
hit tri_on_hex on move=1
hit tri_on_hex on move=3
hit tri_on_hex on move=6
hit tri_on_hex on move=9
hit hex_on_hex on move=2
In [24]:
def single_sq_6443(tiling: Tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 4:
            possible_shapes = tiling.get_possible_shapes_for_edge(edge)
            possible_shapes = set(possible_shapes) - {4}
            if len(possible_shapes) == 1:
                tiling.add_shape_to_edge(possible_shapes.pop(), edge)
            return
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 6:
            if 4 in tiling.get_possible_shapes_for_edge(edge):
                tiling.add_shape_to_edge(4, edge)
            return
    raise NoMovesError
    
tiling = Tiling([6, 4, 4, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 200, single_sq_6443)
# tiling.add_shape_to_edge(4, (12,13))
# tiling.run(6)


plot = Plot(tiling)
plot.plotly_iplot()
hit single_sq_6443 on move=0
hit single_sq_6443 on move=1
hit single_sq_6443 on move=3
hit single_sq_6443 on move=6
hit single_sq_6443 on move=10
hit single_sq_6443 on move=14
hit single_sq_6443 on move=18
hit single_sq_6443 on move=22
hit single_sq_6443 on move=26
hit single_sq_6443 on move=32
hit single_sq_6443 on move=36
hit single_sq_6443 on move=42
hit single_sq_6443 on move=48
hit single_sq_6443 on move=52
hit single_sq_6443 on move=58
hit single_sq_6443 on move=62
hit single_sq_6443 on move=66
hit single_sq_6443 on move=72
hit single_sq_6443 on move=76
hit single_sq_6443 on move=80
hit single_sq_6443 on move=86
hit single_sq_6443 on move=90
hit single_sq_6443 on move=96
hit single_sq_6443 on move=102
hit single_sq_6443 on move=106
hit single_sq_6443 on move=112
hit single_sq_6443 on move=118
hit single_sq_6443 on move=122
hit single_sq_6443 on move=128
hit single_sq_6443 on move=134
hit single_sq_6443 on move=138
hit single_sq_6443 on move=144
hit single_sq_6443 on move=148
hit single_sq_6443 on move=154
hit single_sq_6443 on move=158
hit single_sq_6443 on move=164
hit single_sq_6443 on move=170
hit single_sq_6443 on move=176
hit single_sq_6443 on move=180
hit single_sq_6443 on move=186
hit single_sq_6443 on move=192
hit single_sq_6443 on move=198
In [25]:
def many_sq_6443(tiling: Tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 4:
            if 4 in tiling.get_possible_shapes_for_edge(edge):
                tiling.add_shape_to_edge(4, edge)
            return
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 6:
            if 4 in tiling.get_possible_shapes_for_edge(edge):
                tiling.add_shape_to_edge(4, edge)
            return
    raise NoMovesError
    
tiling = Tiling([6, 4, 4, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 200, many_sq_6443)
# tiling.add_shape_to_edge(4, (12,13))
# tiling.run(6)


plot = Plot(tiling)
plot.plotly_iplot()
hit many_sq_6443 on move=0
hit many_sq_6443 on move=1
hit many_sq_6443 on move=24
hit many_sq_6443 on move=46
hit many_sq_6443 on move=66
hit many_sq_6443 on move=86
hit many_sq_6443 on move=106
hit many_sq_6443 on move=126
hit many_sq_6443 on move=144
hit many_sq_6443 on move=164
hit many_sq_6443 on move=184
In [26]:
def double_sq_6443(tiling: Tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 4:
            for shape_edge in shape_info["edges"]:
                adjacent_shape_sides = [
                    tiling.shapes[s_id]["n_sides"] for s_id in tiling.edges_to_shapes[shape_edge]
                    if s_id != shape_id
                ]
                if adjacent_shape_sides == [4]:
                    possible_shapes = set(tiling.get_possible_shapes_for_edge(edge)) - {4}
                    if len(possible_shapes) == 1:
                        tiling.add_shape_to_edge(possible_shapes.pop(), edge)
                        return
            # no adjacent square
            if 4 in tiling.get_possible_shapes_for_edge(edge):
                tiling.add_shape_to_edge(4, edge)
                return
            
    raise NoMovesError
    
tiling = Tiling([6, 4, 4, 3])
tiling.add_first_shape(6)
tiling.add_shape_to_edge(4, (0, 1))
# tiling.add_shape_to_edge(4, (6, 7))
meta_tiling(tiling, 250, double_sq_6443)


plot = Plot(tiling)
plot.plotly_iplot()
hit double_sq_6443 on move=0
hit double_sq_6443 on move=23
hit double_sq_6443 on move=27
hit double_sq_6443 on move=31
hit double_sq_6443 on move=35
hit double_sq_6443 on move=39
hit double_sq_6443 on move=43
hit double_sq_6443 on move=47
hit double_sq_6443 on move=67
hit double_sq_6443 on move=89
hit double_sq_6443 on move=111
hit double_sq_6443 on move=133
hit double_sq_6443 on move=155
hit double_sq_6443 on move=179
hit double_sq_6443 on move=183
hit double_sq_6443 on move=187
hit double_sq_6443 on move=209
hit double_sq_6443 on move=213
hit double_sq_6443 on move=235
hit double_sq_6443 on move=239
In [27]:
   
tiling = Tiling([6, 4, 4, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 100, single_sq_6443)
meta_tiling(tiling, 500, many_sq_6443)
# tiling.add_shape_to_edge(4, (12,13))
# tiling.run(6)


plot = Plot(tiling)
plot.plotly_iplot()
hit single_sq_6443 on move=0
hit single_sq_6443 on move=1
hit single_sq_6443 on move=3
hit single_sq_6443 on move=6
hit single_sq_6443 on move=10
hit single_sq_6443 on move=14
hit single_sq_6443 on move=18
hit single_sq_6443 on move=22
hit single_sq_6443 on move=26
hit single_sq_6443 on move=32
hit single_sq_6443 on move=36
hit single_sq_6443 on move=42
hit single_sq_6443 on move=48
hit single_sq_6443 on move=52
hit single_sq_6443 on move=58
hit single_sq_6443 on move=62
hit single_sq_6443 on move=66
hit single_sq_6443 on move=72
hit single_sq_6443 on move=76
hit single_sq_6443 on move=80
hit single_sq_6443 on move=86
hit single_sq_6443 on move=90
hit single_sq_6443 on move=96
hit many_sq_6443 on move=2
hit many_sq_6443 on move=28
hit many_sq_6443 on move=48
hit many_sq_6443 on move=68
hit many_sq_6443 on move=86
hit many_sq_6443 on move=106
hit many_sq_6443 on move=128
hit many_sq_6443 on move=146
hit many_sq_6443 on move=168
hit many_sq_6443 on move=190
hit many_sq_6443 on move=212
hit many_sq_6443 on move=234
hit many_sq_6443 on move=252
hit many_sq_6443 on move=272
hit many_sq_6443 on move=290
hit many_sq_6443 on move=310
hit many_sq_6443 on move=332
hit many_sq_6443 on move=350
hit many_sq_6443 on move=370
hit many_sq_6443 on move=388
hit many_sq_6443 on move=408
hit many_sq_6443 on move=428
hit many_sq_6443 on move=446
hit many_sq_6443 on move=468
hit many_sq_6443 on move=490
In [28]:
   
tiling = Tiling([6, 4, 4, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 50, many_sq_6443)
meta_tiling(tiling, 200, single_sq_6443)
meta_tiling(tiling, 300, double_sq_6443)
# tiling.add_shape_to_edge(4, (12,13))
# tiling.run(6)


plot = Plot(tiling)
plot.plotly_iplot()
hit many_sq_6443 on move=0
hit many_sq_6443 on move=1
hit many_sq_6443 on move=24
hit many_sq_6443 on move=46
hit single_sq_6443 on move=16
hit single_sq_6443 on move=22
hit single_sq_6443 on move=28
hit single_sq_6443 on move=32
hit single_sq_6443 on move=36
hit single_sq_6443 on move=42
hit single_sq_6443 on move=46
hit single_sq_6443 on move=50
hit single_sq_6443 on move=54
hit single_sq_6443 on move=58
hit single_sq_6443 on move=64
hit single_sq_6443 on move=70
hit single_sq_6443 on move=76
hit single_sq_6443 on move=82
hit single_sq_6443 on move=88
hit single_sq_6443 on move=94
hit single_sq_6443 on move=100
hit single_sq_6443 on move=106
hit single_sq_6443 on move=112
hit single_sq_6443 on move=118
hit single_sq_6443 on move=122
hit single_sq_6443 on move=126
hit single_sq_6443 on move=132
hit single_sq_6443 on move=136
hit single_sq_6443 on move=140
hit single_sq_6443 on move=146
hit single_sq_6443 on move=152
hit single_sq_6443 on move=158
hit single_sq_6443 on move=162
hit single_sq_6443 on move=166
hit single_sq_6443 on move=172
hit single_sq_6443 on move=178
hit single_sq_6443 on move=184
hit single_sq_6443 on move=190
hit single_sq_6443 on move=196
hit double_sq_6443 on move=2
hit double_sq_6443 on move=22
hit double_sq_6443 on move=40
hit double_sq_6443 on move=60
hit double_sq_6443 on move=82
hit double_sq_6443 on move=100
hit double_sq_6443 on move=120
hit double_sq_6443 on move=138
hit double_sq_6443 on move=158
hit double_sq_6443 on move=180
hit double_sq_6443 on move=198
hit double_sq_6443 on move=218
hit double_sq_6443 on move=236
hit double_sq_6443 on move=254
hit double_sq_6443 on move=278
hit double_sq_6443 on move=284
hit double_sq_6443 on move=290
hit double_sq_6443 on move=296
In [29]:
import random
def random_shape(tiling: Tiling):
    edges = list(tiling.iter_open_edges())
    random.seed(tuple(edges))
    edge = random.choice(edges)
    possible_shapes = tiling.get_possible_shapes_for_edge(edge)
    n_sides = random.choice(possible_shapes)
    tiling.add_shape_to_edge(n_sides, edge)
            
tiling = Tiling([6, 4, 4, 3])
tiling.add_first_shape(6)
meta_tiling(tiling, 250, random_shape)


plot = Plot(tiling)
plot.plotly_iplot()
hit random_shape on move=0
hit random_shape on move=24
hit random_shape on move=28
hit random_shape on move=32
hit random_shape on move=36
hit random_shape on move=64
hit random_shape on move=92
hit random_shape on move=96
hit random_shape on move=97
hit random_shape on move=105
hit random_shape on move=111
hit random_shape on move=117
hit random_shape on move=118
hit random_shape on move=137
hit random_shape on move=138
hit random_shape on move=142
hit random_shape on move=164
hit random_shape on move=173
hit random_shape on move=194
hit random_shape on move=224
hit random_shape on move=244
In [30]:
def many_square(tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 4:
            if 4 in tiling.get_possible_shapes_for_edge(edge):
                tiling.add_shape_to_edge(4, edge)
            return
        
tiling = Tiling([3, 3, 3, 4, 4])
tiling.add_first_shape(4)
meta_tiling(tiling, 297, many_square)

plot = Plot(tiling)
plot.plotly_iplot()
hit many_square on move=0
hit many_square on move=7
hit many_square on move=22
hit many_square on move=43
hit many_square on move=70
hit many_square on move=103
hit many_square on move=142
hit many_square on move=187
hit many_square on move=238
hit many_square on move=295
In [31]:
def single_square(tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 4:
            possible_sides = set(tiling.get_possible_shapes_for_edge(edge)) - {4}
            if len(possible_sides) == 1:
                tiling.add_shape_to_edge(possible_sides.pop(), edge)
                return
        elif 3 in tiling.get_possible_shapes_for_edge(edge):
            tiling.add_shape_to_edge(3, edge)
            return
    raise NoMovesError
        
tiling = Tiling([3, 3, 3, 4, 4])
tiling.add_first_shape(4)
meta_tiling(tiling, 100, single_square)

plot = Plot(tiling)
plot.plotly_iplot()
hit single_square on move=0
hit single_square on move=1
hit single_square on move=2
hit single_square on move=3
hit single_square on move=4
hit single_square on move=6
hit single_square on move=17
hit single_square on move=19
hit single_square on move=23
hit single_square on move=27
In [32]:
def double_tri(tiling: Tiling):
    for edge in tiling.iter_open_edges():
        shape_id = getitem(tiling.edges_to_shapes[edge])
        shape_info = tiling.shapes[shape_id]
        if shape_info["n_sides"] == 3:
            for shape_edge in shape_info["edges"]:
                adjacent_shape_sides = [
                    tiling.shapes[s_id]["n_sides"] for s_id in tiling.edges_to_shapes[shape_edge]
                    if s_id != shape_id
                ]
                if adjacent_shape_sides == [3]:
                    possible_shapes = set(tiling.get_possible_shapes_for_edge(edge)) - {3}
                    if len(possible_shapes) == 1:
                        tiling.add_shape_to_edge(possible_shapes.pop(), edge)
                        return
        if 3 in tiling.get_possible_shapes_for_edge(edge):
            tiling.add_shape_to_edge(3, edge)
            return
            
    raise NoMovesError
    
tiling = Tiling([3, 3, 3, 4, 4])
tiling.add_first_shape(4)
meta_tiling(tiling, 100, double_tri)


plot = Plot(tiling)
plot.plotly_iplot()
hit double_tri on move=0
hit double_tri on move=1
hit double_tri on move=2
hit double_tri on move=3
hit double_tri on move=4
hit double_tri on move=6
hit double_tri on move=8
hit double_tri on move=10
hit double_tri on move=12
hit double_tri on move=16
hit double_tri on move=20
hit double_tri on move=21
hit double_tri on move=24
hit double_tri on move=25
hit double_tri on move=28
hit double_tri on move=29
hit double_tri on move=31
hit double_tri on move=36
hit double_tri on move=39
hit double_tri on move=43
hit double_tri on move=45
hit double_tri on move=48
hit double_tri on move=49
hit double_tri on move=54
hit double_tri on move=57
hit double_tri on move=61
hit double_tri on move=64
hit double_tri on move=68
hit double_tri on move=70
hit double_tri on move=73
hit double_tri on move=78
hit double_tri on move=82
hit double_tri on move=85
hit double_tri on move=90
hit double_tri on move=97
hit double_tri on move=99
In [33]:
tiling = Tiling([3, 3, 3, 4, 4])
tiling.add_first_shape(4)
tiling.add_shape_to_edge(3, (0, 1))
tiling.add_shape_to_edge(4, (0, 4))
meta_tiling(tiling, 200, random_shape)

plot = Plot(tiling)
plot.plotly_iplot()
hit random_shape on move=2
hit random_shape on move=3
hit random_shape on move=4
hit random_shape on move=6
hit random_shape on move=9
hit random_shape on move=14
hit random_shape on move=19
hit random_shape on move=28
hit random_shape on move=33
hit random_shape on move=44
In [ ]: